﻿namespace SenenFernandez.SmartExtensionMethods
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;

    /// <summary>
    /// Extension Methods for Tracing.
    /// </summary>
    public static class SmartTrace
    {
        public static string FileName
        {
            get { return SmartLogger.Instance.FileName; }
            set { SmartLogger.Instance.FileName = value; }
        }

        /// <summary>
        /// Trace objects.
        /// </summary>
        public static void Log<T>(this T value, bool consoleOutput = false)
        {
            SmartLogger.Instance.Store(value, consoleOutput);
        }

        /// <summary>
        /// Displays an exceptions.
        /// </summary>
        public static void Show(this Exception ex)
        {
            SmartLogger.Instance.Store(ex, true);
            MessageBox.Show(SmartLogger.Instance.GetMessage(ex), "Exception", MessageBoxButton.OK, MessageBoxImage.Error);
        }

        /// <summary>
        /// Starts the process.
        /// </summary>
        public static void Start()
        {
            SmartLogger.Instance.Start();
        }

        public static void Stop()
        {
            SmartLogger.Instance.Dispose();
        }
    }

    /// <summary>
    /// The File Log helper.
    /// </summary>
    public class SmartLogger : IDisposable
    {
        private static readonly object sync = new object();
        private static SmartLogger instance;
        private ManualResetEvent worker;
        private ConcurrentQueue<Log> queue;
                
        /// <summary>
        /// Singleton Instance.
        /// </summary>
        public static SmartLogger Instance
        {
            get
            {
                lock (sync)
                {
                    return instance ?? (instance = new SmartLogger());
                }
            }
        }

        /// <summary>
        /// Gets the logging filen ame.
        /// </summary>
        public string FileName { get; set; }

        /// <summary>
        /// Gets if the Singleton is already created.
        /// </summary>
        public bool IsReady { get; private set; }

        /// <summary>
        /// Occurs when an entry is written to an event log queue.
        /// </summary>
        public event EventHandler<SmartLoggerEventArgs> Logging;

        /// <summary>
        /// Starts the process.
        /// </summary>
        public bool Start()
        {
            lock (sync)
            {
                if (!IsReady)
                {
                    if (string.IsNullOrEmpty(FileName))
                    {
                        FileName = string.Format("{0}.log", Process.GetCurrentProcess().MainModule.ModuleName);
                    }
                    worker = new ManualResetEvent(false);
                    queue = new ConcurrentQueue<Log>();
                    IsReady = true;
                    Task.Factory.StartNew(
                        () =>
                        {
                            do
                            {
                                worker.WaitOne();
                                Dequeue();
                            } while (IsReady);
                        });
                }
            }
            return IsReady;
        }

        /// <summary>
        /// Log messages and exceptions in thread safe environment.
        /// </summary>
        public void Store<T>(T value, bool consoleOutput = false)
        {
            Start();
            lock (sync)
            {
                if (IsReady)
                {
                    var log = new Log(value, consoleOutput);
                    queue.Enqueue(log);
                    if (Logging != null)
                    {
                        Logging.Invoke(this, new SmartLoggerEventArgs(log));
                    }
                    worker.Set();
                }
            }
        }

        public string GetMessage(Exception ex)
        {
            string detail = default(string);
            var trace = new System.Diagnostics.StackTrace(ex, true);
            if (trace.FrameCount > 0)
            {
                var frame = trace.GetFrame(trace.FrameCount - 1);
                var method = frame.GetMethod();
                var lineNumber = frame.GetFileLineNumber();
                detail = string.Format("[{0}.{1}({2})]{3}{3}", method.ReflectedType.Name, method.Name, lineNumber, Environment.NewLine);
            }
            var exception = ex.InnerException ?? ex;
            while (exception.InnerException != null)
            {
                exception = exception.InnerException;
            }
            return string.Format("{0}{1}", detail, exception.Message);
        }

        /// <summary>
        /// Destroy the object.
        /// </summary>
        public void Dispose()
        {
            try
            {
                IsReady = false;
                worker.Set();
                worker.Close();
                if (Logging != null)
                {
                    foreach (EventHandler<SmartLoggerEventArgs> handler in Logging.GetInvocationList())
                    {
                        Logging -= handler;
                    }
                }
            }
            finally
            {
                GC.SuppressFinalize(this);
            }
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~SmartLogger()
        {
            Dispose();
        }

        private void Dequeue()
        {
            var list = new List<Log>();
            lock (sync)
            {
                while (!queue.IsEmpty && IsReady)
                {
                    Log log;
                    queue.TryDequeue(out log);
                    list.Add(log);
                }
            }
            Save(list);
            if (worker != null)
            {
                worker.Reset();
            }
        }

        private void Save(List<Log> values)
        {
            if (values.Count > 0)
            {
                string path = Path.GetDirectoryName(FileName);
                if (path == null)
                {
                    return;
                }
                if (!string.IsNullOrEmpty(path) && !Directory.Exists(path))
                {
                    Directory.CreateDirectory(path);
                }
                using (var outfile = new StreamWriter(FileName, true))
                {
                    values.ForEach(outfile.WriteLine);
                }
                var outputValues = values.Where(source => source.ConsoleOutput);
                foreach (var outputValue in outputValues)
                {
                    Console.WriteLine(outputValue);
                }
            }
        }
    }

    /// <summary>
    /// Log class.
    /// </summary>
    public class Log
    {
        public DateTimeOffset Date { get; private set; }

        public object Value { get; set; }

        public bool ConsoleOutput { get; set; }

        public Log(object value, bool consoleOutput = false)
        {
            Date = DateTimeOffset.Now;
            Value = value;
            ConsoleOutput = consoleOutput;
        }

        public override string ToString()
        {
            return string.Format("Date: {0} Value: {1}", Date, Value);
        }
    }

    public class SmartLoggerEventArgs : EventArgs
    {
        public DateTimeOffset Timestamp { get; private set; }

        public Log Log { get; private set; }

        public SmartLoggerEventArgs(Log log)
        {
            Timestamp = DateTimeOffset.Now;
            Log = log;
        }
    }
}